# 清除当前 R 环境中的所有变量，`all = T` 表示清除所有对象，确保环境干净
rm(list = ls(all = T))

# 加载 `survival` 包，该包提供了生存分析所需的函数和工具，如 `survfit` 和 `coxph` 等
library(survival)
# 加载 `survminer` 包，用于绘制生存分析相关的图形，如生存曲线
library(survminer)
# 加载 `ggpubr` 包，提供了一系列用于增强 `ggplot2` 绘图功能的工具，方便进行图形的自定义和美化
library(ggpubr)
# 加载 `ggtext` 包，支持在 `ggplot2` 图形中使用富文本标签，可用于更灵活地设置文本样式
library(ggtext)

# 设置工作目录，指定 R 代码运行时查找和保存文件的默认位置
setwd("D:/马明福/新文章/survivallx")

# 定义要读取的数据文件路径
file_path <- "da.csv"
# 检查指定路径的文件是否存在
if (!file.exists(file_path)) {
  # 如果文件不存在，停止程序并输出错误信息提示用户检查文件路径
  stop(paste("文件", file_path, "不存在，请检查文件路径。"))
}

# 使用 `read.table` 函数读取 CSV 文件数据，`sep = ","` 表示文件以逗号分隔列，`header = T` 表示第一行是列名
df <- read.table(file_path, sep = ",", header = T)

# 查看数据框 `df` 的结构，包括列名、数据类型和前几行的值等信息，帮助了解数据情况
str(df)
#筛选出分层的数据，并形成新数据框
df <- df[df$sex==2, ]

# 定义生存分析所需的必要列名
required_cols <- c("timepfs", "statuspfs", "UBE2Sc")
# 检查数据框的列名是否包含所有必要列
if (!all(required_cols %in% colnames(df))) {
  # 如果有必要列缺失，找出缺失的列名
  missing_cols <- required_cols[!required_cols %in% colnames(df)]
  # 停止程序并输出错误信息，告知用户数据框中缺少哪些必要列
  stop(paste("数据框中缺少必要列：", paste(missing_cols, collapse = ", ")))
}

# 使用 `survfit` 函数进行生存分析，`Surv(time = df$timeos, df$status)` 定义生存时间和事件状态，
# `~ df$UBE2Sc` 表示按 `UBE2Sc` 变量的不同水平进行分组分析，结果存储在 `fit` 中
fit <- survfit(Surv(time = df$timepfs, df$statuspfs) ~ df$UBE2Sc, data = df)

# 使用 `coxph` 函数进行 Cox 比例风险回归分析，`Surv(timeos, status)` 定义生存时间和事件状态，
# `~ UBE2Sc` 表示以 `UBE2Sc` 作为协变量进行回归分析，结果存储在 `cox_fit` 中
cox_fit <- coxph(Surv(timepfs, statuspfs) ~ UBE2Sc, data = df)
# 对 Cox 回归结果进行总结，包含 HR、置信区间、p 值等信息
summary_cox <- summary(cox_fit)
# 提取 `UBE2Sc` 变量的风险比（HR），并保留两位小数
HR <- round(summary_cox$coefficients["UBE2Sc", "exp(coef)"], 2)
# 提取 `UBE2Sc` 变量的 HR 的 95% 置信区间下限，并保留两位小数
HR_low <- round(summary_cox$conf.int["UBE2Sc", "lower .95"], 2)
# 提取 `UBE2Sc` 变量的 HR 的 95% 置信区间上限，并保留两位小数
HR_high <- round(summary_cox$conf.int["UBE2Sc", "upper .95"], 2)
# 提取 `UBE2Sc` 变量的原始 p 值
p_value_raw <- summary_cox$coefficients["UBE2Sc", "Pr(>|z|)"]

# 根据 p 值情况设置显示内容
if (p_value_raw < 0.001) {
  # 如果 p 值小于 0.001，显示为 "P < 0.001"
  p_value <- "P < 0.001"
} else {
  # 否则，将 p 值保留三位有效数字并显示为 "P = xxx" 的形式
  p_value <- format(p_value_raw, digits = 3, scientific = FALSE)
  p_value <- paste0("P = ", p_value)
}

# 自定义一个函数，用来更改各种样式
customize_labels <- function(p, font.title = NULL,
                             font.subtitle = NULL, font.caption = NULL,
                             font.x = NULL, font.y = NULL, font.xtickslab = NULL, font.ytickslab = NULL,
                             font.legend = NULL, font.table_title = NULL, font.table_text = NULL) {
  # 保存原始的图形对象
  original.p <- p
  # 判断输入的 `p` 是否为 `ggplot` 对象，如果是则将其转换为列表形式
  if (is.ggplot(original.p)) list.plots <- list(original.p)
  # 判断输入的 `p` 是否为列表，如果是则直接使用该列表
  else if (is.list(original.p)) list.plots <- original.p
  # 如果输入既不是 `ggplot` 对象也不是列表，则抛出错误
  else stop("Can't handle an object of class ", class(original.p))
  
  # 定义一个内部函数，用于解析字体设置并转换为 `ggtext::element_markdown` 对象
  .set_font <- function(font) {
    font <- ggpubr:::.parse_font(font)
    ggtext::element_markdown(size = font$size, face = font$face, colour = font$color)
  }
  
  # 遍历列表中的每个图形对象
  for (i in 1:length(list.plots)) {
    p <- list.plots[[i]]
    # 确保当前对象是 `ggplot` 对象
    if (is.ggplot(p)) {
      # 如果指定了标题字体，则更新图形的标题字体样式
      if (!is.null(font.title)) p <- p + theme(plot.title = .set_font(font.title))
      # 如果指定了副标题字体，则更新图形的副标题字体样式
      if (!is.null(font.subtitle)) p <- p + theme(plot.subtitle = .set_font(font.subtitle))
      # 如果指定了图注字体，则更新图形的图注字体样式
      if (!is.null(font.caption)) p <- p + theme(plot.caption = .set_font(font.caption))
      # 如果指定了 x 轴标题字体，则更新图形的 x 轴标题字体样式
      if (!is.null(font.x)) p <- p + theme(axis.title.x = .set_font(font.x))
      # 如果指定了 y 轴标题字体，则更新图形的 y 轴标题字体样式
      if (!is.null(font.y)) p <- p + theme(axis.title.y = .set_font(font.y))
      # 如果指定了 x 轴刻度标签字体，则更新图形的 x 轴刻度标签字体样式
      if (!is.null(font.xtickslab)) p <- p + theme(axis.text.x = .set_font(font.xtickslab))
      # 如果指定了 y 轴刻度标签字体，则更新图形的 y 轴刻度标签字体样式
      if (!is.null(font.ytickslab)) p <- p + theme(axis.text.y = .set_font(font.ytickslab))
      # 如果指定了图例字体，则更新图形的图例字体样式
      if (!is.null(font.legend)) p <- p + theme(legend.text = .set_font(font.legend))
      
      # 处理风险表标题
      if (!is.null(p$table) && !is.null(font.table_title)) {
        # 如果存在风险表且指定了风险表标题字体，则更新风险表的标题字体样式
        p$table <- p$table + theme(plot.title = .set_font(font.table_title))
      }
      
      # 处理风险表文本
      if (!is.null(p$table) && !is.null(font.table_text)) {
        p$table <- p$table + theme(text = .set_font(font.table_text))
      }
      
      # 将更新后的图形对象放回列表中
      list.plots[[i]] <- p
    }
  }
  
  # 如果原始输入是单个 `ggplot` 对象，则返回更新后的单个图形对象
  if (is.ggplot(original.p)) list.plots[[1]]
  # 否则返回更新后的图形对象列表
  else list.plots
}

# 绘制生存曲线并添加风险表
ggsurv <- ggsurvplot(
  fit,  # 生存分析的结果对象
  data = df,  # 绘图使用的数据框
  size = 1,  # 曲线的线条粗细
  palette = c("#E7B800", "#2E9FDF"),  # 设置不同组别的颜色
  conf.int = TRUE,  # 是否显示置信区间
  pval = FALSE,  # 先不显示默认的 p 值
  log.rank.weights = "1",  # 对数秩检验的权重设置
  risk.table = TRUE,  # 是否添加风险表
  risk.table.col = "strata",  # 风险表按分组显示颜色
  legend.labs = c("UBE2S=Negative", "UBE2S=Positive"),  # 图例标签
  ggtheme = theme_light() + theme(panel.grid = element_blank()),  # 去除网格线，使用简洁主题
  xlab = "Time in months",  # x 轴标签
  risk.table.y.text.col = T,  # 风险表的 y 轴文本是否显示颜色
  risk.table.y.text = FALSE,  # 是否显示风险表的 y 轴文本
  xlim = c(0, 90),  # x 轴的显示范围
  legend = c(0.2, 0.15),  # 设置图例位置在左下角
  legend.title = "",  # 去除图例标题
  risk.table.fontsize = 6,  # 调整风险表格字体大小，可间接影响颜色条大小
  risk.table.height = 0.25,  # 风险表占整个图形的高度比例
)

# 再次确保 p_value 只包含三位有效数字
if (p_value_raw >= 0.001) {
  p_value <- sub("P = (.*)", "P = \\1", format(round(as.numeric(sub("P = ", "", p_value)), 3), nsmall = 3))
}

# 构建换行的 p 值文本，包含 HR 和 95% 置信区间以及 p 值信息，将 HR 和 p 值部分都加粗，P = 斜体
pval_text <- paste0("<b>HR = ", HR, " (", HR_low, "-", HR_high, ")</b><br><b><i>P = </i>", sub("^P = ", "", p_value), "</b>")

# 添加自定义的 p 值文本，增大字体大小
ggsurv$plot <- ggsurv$plot + 
  annotate(
    geom = "richtext",  # 使用富文本注释
    x = max(ggsurv$plot$data$time) * 0.75,  # 调整 x 位置
    y = max(ggsurv$plot$data$surv) * 0.99,  # 调整 y 位置
    label = pval_text,  # 注释的文本内容
    hjust = 1, vjust = 1,  # 文本的对齐方式
    size = 8,  # 增大字体大小
    fill = NA,  # 注释框的填充颜色
    label.color = NA  # 注释框的边框颜色
  )

# 更改生存曲线的标签
ggsurv$plot <- ggsurv$plot + labs(
  title = "Progression free survival curves",  # 图形标题
  subtitle = "Female",  # 副标题
  caption = ""  # 图注
)

# 更改 risk table 的标签
ggsurv$table <- ggsurv$table + labs(
  title = "Number at risk",  # 风险表标题
  caption = ""  # 风险表图注
)

# 更改生存曲线，risk table 的字体大小、类型、颜色
ggsurv <- customize_labels(
  ggsurv,
  font.title = c(24, "bold", "darkblue"),  # 标题字体设置
  font.subtitle = c(24, "bold.italic", "purple"),  # 副标题字体设置
  font.caption = c(24, "plain", "orange"),  # 图注字体设置
  font.x = c(18, "bold.italic", "red"),  # x 轴标题字体设置
  font.y = c(24, "bold.italic", "darkred"),  # y 轴标题字体设置
  font.xtickslab = c(18, "bold"),  # x 轴刻度标签字体设置
  font.ytickslab = c(18, "bold"),  # y 轴刻度标签字体设置
  font.legend = c(18, "plain", "black"),  # 图例字体设置
  font.table_title = c(18, "bold", "black"),  # 风险表标题字体设置
  font.table_text = c(30, "bold")  # 风险表文本字体设置
)

# 显示图形
ggsurv
library(Cairo)
CairoPNG("FemaleUBE2SPFS.png", width = 2800, height = 2800, dpi = 300)
print(ggsurv)
dev.off()
